class Actor
  class Trace
    attr_reader :from, :to
    attr_reader :delta, :length, :normal
    attr_reader :left, :right, :top, :bottom
    attr_reader :width, :height
    attr_reader :first_hit, :hits
    
    def initialize(from, to, options={})
      # FIXME: trace diagonals and verticals as well
      raise 'trace only implemented for horizontal lines' unless from.y == to.y
      @from = from
      @to = to
      @classes = options[:classes]
      @classes = [@classes] if @classes and !@classes.kind_of? Array
      @ignore = options[:ignore]
      @ignore = [@ignore] if @ignore and !@ignore.kind_of? Array
      calculate_size
      clear
      collide
    end
    
    def calculate_size
      @delta = @from - @to
      @length = @delta.length
      @normal = @length == 0 ? Point.new(0, 0) : @delta / @length
      
      xs, ys = [from.x, to.x], [from.y, to.y]
      @left, @right = xs.min, xs.max
      @top, @bottom = ys.min, ys.max
      @width = @right - @left
      @height = @bottom - @top
    end
    
    def clear
      @first_hit = nil
      @first_hit_time = nil
      @hits = {}
    end
    
    def collide
      # FIXME: use collision if length == 0
      Actor.each do |a|
        next if @ignore.include? a
        next if @left > a.right
        next if @right < a.left
        next if @top > a.bottom
        next if @bottom < a.top
        if @length == 0
          t = 0
        elsif @from.x < @to.x
          t = (a.left - @from.x) / @width
        else
          t = (a.right - @to.x) / @width
        end
        hit a, t
      end
      self
    end
    
    def each_hit
      @hits.each_pair do |actor, time|
        yield actor, time
      end
    end
    
    def empty?
      @hits.empty?
    end
    
    def hit(actor, time)
      @hits[actor] = time
      if @first_time.nil? or time < @first_time
        @first_hit = actor
        @first_hit_time = time
      end
    end
    
    def hit?(actor=nil)
      return !@hits.empty? if actor.nil?
      @hit.has_key? actor
    end
    
    def time(actor=nil)
      return @first_hit_time || 1 if actor.nil?
      @hits[actor]
    end
  end
  
  def trace(from, to, options={})
    from, to = from.clone, to.clone
    if options[:width]
      options[:width] *= -1 if from.x > to.x
      from.x -= options[:width]
      to.x += options[:width]
    end
    if options[:height]
      options[:height] *= -1 if from.y > to.y
      from.y -= options[:height]
      to.y += options[:height]
    end
    options[:ignore] ||= []
    options[:ignore] = [options[:ignore]] unless options[:ignore].kind_of? Array
    options[:ignore] << self
    Actor::Trace.new from, to, options
  end
end
